#include <GL/freeglut.h> // include freeglut before stdlib
#include <iostream>
#include <stdlib.h>
#include <stdio.h>

#include <time.h>

#include <vector>
using std::vector;
using std::endl;
using std::cout;


class Heightmap
{
private:
	vector<GLfloat> vertColors;
	vector<GLfloat> texCoords;
	vector<GLfloat> normals;
	vector<GLfloat> vertices;
	vector<GLuint> indices;	

	int heightmapWidth;
	int heightmapLength;
	vector< vector<float> > heights;

	void createVertexBufferData()
	{
		vertColors = vector<GLfloat> ( 3 * heightmapWidth*heightmapLength, 0.0f );
		texCoords = vector<GLfloat>( 2 * heightmapWidth*heightmapLength,  0.0f );
		normals = vector<GLfloat> ( 3 * heightmapWidth*heightmapLength, 0.0f );
		vertices = vector<GLfloat> ( 3 * heightmapWidth*heightmapLength,  0.0f );
		size_t texCoordIdx = 0;
		size_t vertIdx = 0;
		size_t normIdx = 0;

		// Vertices & normals & tex coordinates:
		for(size_t z=0; z<heightmapLength; z++)
		{
			for(size_t x=0; x<heightmapWidth; x++)
			{
				float middleX = ( float(heightmapWidth)-1.0f) / 2.0f;
				float middleZ = ( float(heightmapLength)-1.0f) / 2.0f;
				vertices[vertIdx++] = float(x) - middleX;
				vertices[vertIdx++] = heights[x][z];
				vertices[vertIdx++] = float(z) - middleZ;

				texCoords[texCoordIdx++] = (float) x / (heightmapWidth-1);
				texCoords[texCoordIdx++] = (float) z / (heightmapLength-1);
			}
		}
		// Eof vertices.

		//copy normals into colors...
		//for(size_t i=0; i<vertColors.size(); i++)
		//	vertColors[i] = 0.8f;

		size_t idx = 0;
		for(size_t x = 0; x<heightmapWidth; x++)
			for(size_t z = 0; z < heightmapLength; z++)
			{
				vertColors[idx + 0] = float(x) / float(heightmapWidth);
				vertColors[idx + 1] = 0;
				vertColors[idx + 2] = float(z) / float(heightmapLength);
				idx+=3;
			}

			size_t numIndices = (heightmapWidth*2) * (heightmapLength - 1) + (heightmapLength -2 );
			indices = vector<GLuint>( numIndices, 0);

			size_t index = 0;
			for(size_t z=0; z<heightmapLength-1; z++)
			{
				size_t x = 0;
				if( z % 2 == 0)
				{
					// Even row go left
					for(x=0; x<heightmapWidth; x++)
					{
						// column-major array ordering
						indices[index++] = x + (z * heightmapWidth); // bottom
						indices[index++] = x + (z * heightmapWidth) + heightmapWidth; // top, 1 up Z-wise
					}

					// not last row? then make degenerate vert
					if( z != heightmapLength-2)
					{
						indices[index++] = (--x) + (z * heightmapWidth); // negative X?
					}
				}
				else
				{
					// Odd row go right
					for(x=heightmapWidth-1; x != -1; x--)
					{
						indices[index++] = x + (z * heightmapWidth); // bottom
						indices[index++] = x + (z * heightmapWidth) + heightmapWidth; // top, 1 up Z-wise
					}

					// not last row? then make degenerate vert
					if( z != heightmapLength-2)
					{
						// notice post inc of x value from -1 to 0.
						indices[index++] = ++x + (z * heightmapWidth);
					}
				}
			}

			size_t vertColorKiloBytes = sizeof(GLfloat) * vertColors.size() / 1024;
			size_t texCoordKiloBytes = sizeof(GLfloat) * texCoords.size() / 1024;
			size_t normalsKiloBytes = sizeof(GLfloat) * normals.size() / 1024;
			size_t vertsKiloBytes = sizeof(GLfloat) * vertices.size() / 1024;
			size_t indiceKiloBytes = sizeof(GLuint) * indices.size() / 1024;
			std::cout << "Created heightmap array data: " << std::endl
				<< " \t\tvertcolors(KB) \t  " << vertColorKiloBytes << std::endl
				<< " \t\ttexcoords(KB) \t  " << texCoordKiloBytes << std::endl
				<< " \t\tnormals(KB) \t  " << normalsKiloBytes << std::endl
				<< " \t\tvertices(KB) \t  " << vertsKiloBytes << std::endl
				<< " \t\tindices(KB) \t  " << indiceKiloBytes << std::endl
				<< " \ttotal (MB) = \t  " << (vertColorKiloBytes+texCoordKiloBytes+normalsKiloBytes+vertsKiloBytes+indiceKiloBytes)/1024 << std::endl;

			std::cout << "finish!" << std::endl;
	}


public:
	Heightmap(int heightmapWidth, int heightmapLength)
	{
		this->heightmapWidth = heightmapWidth;
		this->heightmapLength = heightmapLength;
		heights = vector< vector<float> >( heightmapWidth, vector<float>(heightmapLength, 0.0f ) );

		for(size_t y=0; y<heightmapLength; y++)
		{
			for(size_t x=0; x<heightmapWidth; x++)
			{
				heights[x][y] = 0.0;
			}
		}

		createVertexBufferData();
	}




	void render()
	{
		glColor3f(.2f, .2f, .2f);
		//glEnable(GL_TEXTURE_2D);
		//glBindTexture(GL_TEXTURE_2D, texHeightmap);

		//glEnable(GL_LIGHTING);
		//glDisable(GL_LIGHTING); glColor3f(1.0f, 1.0f, 1.0f);

		const int immidiate = 0;
		const int vertexBufferObject = 1;
		const int vertexArrays = 2;

		int renderMode = immidiate;

		switch(renderMode)
		{
		case immidiate:
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			glBegin(GL_TRIANGLE_STRIP);
			for(size_t i=0; i < indices.size(); i++)
			{
				size_t vertIdx = 3 * indices[i];
				size_t texIdx = 2 * indices[i];
				glColor3f(  vertColors[vertIdx+0], vertColors[vertIdx+1], vertColors[vertIdx+2]);
				glTexCoord2f( texCoords[texIdx+0], texCoords[texIdx+1] );
				glNormal3f( normals[vertIdx+0], normals[vertIdx+1], normals[vertIdx+2] );
				glVertex3f( vertices[vertIdx+0], vertices[vertIdx+1], vertices[vertIdx+2] ); 
			}
			glEnd();
			break;
		case vertexBufferObject:
			break;
		case vertexArrays:


			////glEnableClientState(GL_COLOR_ARRAY);
			//glEnableClientState(GL_VERTEX_ARRAY);
			//glEnableClientState(GL_NORMAL_ARRAY);
			//glEnableClientState(GL_TEXTURE_COORD_ARRAY);


			////glColorPointer(3, GL_FLOAT, 0, colors);
			//glTexCoordPointer(2, GL_FLOAT, 0, &texCoords[0]);
			//glNormalPointer(GL_FLOAT, 0 , &normals[0]);
			//glVertexPointer(3, GL_FLOAT, 0 , &vertices[0]);

			//glDrawRangeElements(GL_TRIANGLE_STRIP,			// draw mode
			//	0, (heightmapWidth)*(heightmapLength),		// minimum index, maximum index
			//	indices.size()	,							// number of primitives to draw
			//	GL_UNSIGNED_INT,							// indice datatype
			//	&indices[0] );								// indice array

			//glDisableClientState(GL_VERTEX_ARRAY);
			//glDisableClientState(GL_NORMAL_ARRAY);
			//glDisableClientState(GL_TEXTURE_COORD_ARRAY);

			break;
		default:
			break;
		}


		glDisable(GL_TEXTURE_2D);


		bool showNormals = 0;
		// Show Normals
		if(showNormals)
		{
			glDisable(GL_LIGHTING);
			glColor3f(1,1,0);
			glBegin(GL_LINES);
			for(size_t i=0; i < 3*heightmapWidth*heightmapLength; i+=3)
			{
				glVertex3f( vertices[i+0], vertices[i+1], vertices[i+2] );

				glVertex3f( vertices[i+0] + 0.5f*normals[i+0], 
					vertices[i+1] + 0.5f*normals[i+1], 
					vertices[i+2] + 0.5f*normals[i+2] );
			}
			glEnd();
		}


	}

};

///////////////////////////////////// GLOBALE VERDIER
const int KEY_ID_W        = 0;
const int KEY_ID_S        = 1;
const int KEY_ID_A        = 2;
const int KEY_ID_D        = 3;
const int KEY_ID_SPACE    = 4;
const int KEY_ID_C        = 5;
const int MOUSE_LEFT_BUTTON_DOWN = 20;

double lastframetime;
int window;

bool keyPressed[30];
int mousePosX, mousePosY; 
float moveX, moveY;

Heightmap *heightmap;


void sleep(int ms)
{
  Sleep(ms);
}

double currentTime()
{
  double dTime = GetTickCount() / 1000.0;
  return (dTime);
}

float lerp(float x1, float x2, float t){
	return (x2-x1)*t;
}

void init()
{
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_DEPTH_TEST);

  lastframetime = currentTime();

  for(int i=0; i<30; i++)
    keyPressed[i]=false;

	heightmap = new Heightmap(50,50);
}

void display()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  double now = currentTime();
  //glLoadIdentity();
  glPushMatrix();
  gluLookAt(0,100,-5, 
			0,0,0, 0,1,0);


	

	glPushMatrix();
	glTranslatef(0,1,0);

	heightmap->render();
	glPopMatrix();

  
  glPushMatrix();

	
  
  glBegin(GL_QUADS);
  
  glColor3f(0,1,1);  glVertex3f(-10,0,-10);
  glColor3f(1,1,1);  glVertex3f(10,0,-10);
  glColor3f(1,1,0);  glVertex3f(10,0,10);
  glColor3f(0,1,0);  glVertex3f(-10,0,10);
  
  glEnd();
  
  glPopMatrix();
  
  float color[3];
  glReadPixels( mousePosX, 700-mousePosY, 1, 1, GL_RGB, GL_FLOAT, &color);
  
  float px = -10+(20)*color[0];
  float pz = -10+(20)*(1-color[2]);

  //float px = -10 + lerp(0,20, color[0] );
  //float pz = -10 + lerp(0,20, 1-color[2] );
  
  //if(keyPressed[MOUSE_LEFT_BUTTON_DOWN])
  {
  //glColor3f(color[0],color[1],color[2]);
  glPushMatrix();
  glTranslatef(px,2,pz);
  
  glColor3f(0,0,0);
  glutWireSphere(2,10,10);
  //glColor3f(1,1,1);
  //glutSolidSphere(1.95,10,10);
  glPopMatrix();  
  }
  
  
  glPopMatrix();

  /*
  gm->update(now - lastframetime); // \breif pass in deltatime. second since last frame
  gm->render();
  */
  lastframetime = now;
/*
  if(keyPressed[KEY_ID_W]==true)      gm->getCam()->moveForward();
  if(keyPressed[KEY_ID_A]==true)      gm->getCam()->moveLeft();
  if(keyPressed[KEY_ID_D]==true)      gm->getCam()->moveRight();
  if(keyPressed[KEY_ID_S]==true)      gm->getCam()->moveBackward();
  if(keyPressed[KEY_ID_SPACE]==true)  gm->getCam()->moveUp();
  if(keyPressed[KEY_ID_C]==true)      gm->getCam()->moveDown();
*/
  glutSwapBuffers();
  glutPostRedisplay();
  sleep(16);

}

void keyDown(unsigned char key, int x, int y)
{
  switch (key) 
  {
    case 'q':
    case 27:
      glutDestroyWindow(window);
#ifndef _WIN32
      // Must use this with regular glut, since it never returns control to main().
      exit(0);
#endif
      break;
      
    case 'w':
      keyPressed[KEY_ID_W] = true;
      break;
    case 'a':
      keyPressed[KEY_ID_A] = true;
      break;
    case 's':
      keyPressed[KEY_ID_S] = true;
      break;
    case 'd':
      keyPressed[KEY_ID_D] = true;
      break;
    case ' ':
      keyPressed[KEY_ID_SPACE] = true;
      break;
    case 'c':
      keyPressed[KEY_ID_C] = true;
      break;

    default:
      glutPostRedisplay();
  }
}

void keyUp(unsigned char key, int x, int y)
{
  switch (key) 
  {
    case 'w':
      keyPressed[KEY_ID_W] = false;
      break;
    case 'a':
      keyPressed[KEY_ID_A] = false;
      break;
    case 's':
      keyPressed[KEY_ID_S] = false;
      break;
    case 'd':
      keyPressed[KEY_ID_D] = false;
      break;
    case ' ':
      keyPressed[KEY_ID_SPACE] = false;
      break;
    case 'c':
      keyPressed[KEY_ID_C] = false;
      break;

  }
}

void mousePressed(int button, int state, int posX, int posY)
{
  if(button==GLUT_LEFT_BUTTON && state==GLUT_DOWN)
  {
    mousePosX = posX;
    mousePosY = posY;
    keyPressed[MOUSE_LEFT_BUTTON_DOWN] = true;
  }  
  if(button==GLUT_LEFT_BUTTON && state==GLUT_UP)
    keyPressed[MOUSE_LEFT_BUTTON_DOWN] = false;
}

void mouseMoved(int posX, int posY)
{
  if(keyPressed[MOUSE_LEFT_BUTTON_DOWN])
  {
    int diffX = posX - mousePosX;
    mousePosX = posX;
    int diffY = posY - mousePosY;
    mousePosY = posY;
    
    // Implement quaternion based mouse move

  }
}

void reshape(int w, int h)
{
  glViewport(0, 0, (GLsizei) w, (GLsizei) h); 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
//  glOrtho(-50, 700, -50, 700, -50, 50);
  gluPerspective(60.0f, float(w)/float(h) ,1.0f, 3000.0f);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
//  gluLookAt(0.0, 0.0, 10.0,     0.0, 0.0, 0.0,    0.0, 1.0, 0.0);
}

int main(int argc, char** argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);
  glutInitWindowSize(700, 700); 
  glutInitWindowPosition(10, 10);
  window = glutCreateWindow("colorpicking");
  init();
  glutKeyboardFunc(keyDown);
  glutKeyboardUpFunc(keyUp);
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutMouseFunc(mousePressed);
  glutMotionFunc(mouseMoved);

  // Add other callback functions here..

  glutMainLoop();
  return 0;
}
